import random, sys, traceback, time

from multiprocessing import Process, JoinableQueue, Event
from datetime import datetime

import lib, sql

from xdh_logging import initLog
from catfile import CatFile
from holiday_catfile import HolidayCatFile
from settings import settings
from cache import cache
from nothing import Nothing

log = initLog("tpd")

class TPDWorker(Process):
	Q = JoinableQueue()
	started = Event()

	@classmethod
	def startup(cls):
		if not cls.is_running():
			cls.started.set()
			cls.workers = []
			for i in xrange(settings.data.num_processes):
				worker = Nothing()
				setattr(worker, "running", Event())
				setattr(worker, "process", cls(worker.running))
				cls.workers.append(worker)

	@classmethod
	def is_running(cls):
		if not hasattr(cls, "workers"):
			return False

		running = not (cls.Q.empty() and cls.started.is_set())

		for worker in cls.workers:
			if worker.process.is_alive():
				running |= worker.running.is_set()

		return running

	@classmethod
	def shutdown(cls):
		if not hasattr(cls, "workers"):
			return

		for worker in cls.workers:
			if worker.process.is_alive():
				log.debug("Sending stop to {name} Queue.".format(name = cls.__name__))
				cls.Q.put(lib.STOP)

		cls.Q.join()

		for worker in cls.workers:
			if worker.process.is_alive():
				worker.process.join()

		del cls.workers

		sql.Q.join()

		cls.started.clear()

	def __init__(self, running):
		super(TPDWorker, self).__init__()

		self.running = running

		self.start()

	def initialize(self, cat, fav, hol):
		self.cat = cat
		self.favorite = fav
		self.holiday = hol

		if self.holiday:
			self.weight = lib.holidayWeight(self.cat) * lib.favWeight(self.favorite)
			self.tracks = HolidayCatFile(self.cat, favorites_only = self.favorite)
		else:
			self.weight = lib.holidayWeight() * lib.catWeight(self.cat) * lib.favWeight(self.favorite)
			self.tracks = CatFile(self.cat, favorites_only = self.favorite)

	def process(self):
		self.aMean, self.hMean, self.gMean = lib.getMeans([track.length for track in self.tracks])
		self.value = self.gMean * self.weight
		data = [{
			"category": self.cat,
			"favorite": self.favorite,
			"num_tracks": len(self.tracks),
			"weight": self.weight,
			"amean": self.aMean,
			"hmean": self.hMean,
			"gmean": self.gMean,
			"value": self.value,
		}]
		sql.process(sql.TPDData, "insert", data)

	def run(self):
		try:
			while True:
				item = TPDWorker.Q.get()
				if item == lib.STOP:
					TPDWorker.Q.task_done()
					log.debug("Got stop from {name} Queue.".format(name = TPDWorker.__name__))
					break

				self.running.set()

				if not TPDWorker.started.is_set():
					TPDWorker.started.set()

				log.info("<{cat}>{fav}: Calculating Tracks Per Day.".format(cat = item[0], fav = " [Favorite]" if item[1] else ""))

				self.initialize(*item)
				self.process()

				log.info("<{cat}>{fav}: {gmean} seconds, {val} weighted.".format(cat = self.cat, fav = " [Favorite]" if self.favorite else "", gmean = self.gMean, val = self.value))
                                
				TPDWorker.Q.task_done()

				self.running.clear()
		except Exception, e:
			log.error("{name}: {msg}".format(name = e.__class__.__name__, msg = e))
			log.error("".join(traceback.format_exception(*sys.exc_info())))

	def finish_stats(self):
		sql.process(sql.CatStatData, "insert", self.stats)

	def write_stat(self, step, value):
		if value > 0:
			data = { 
				'timestamp': datetime.today(),
				'category': self.cat,
				'step': step,
				'value': value,
			}

			self.stats.append(data)


class TPD(object):
	calculating = Event()

	def __init__(self):
		pass

	def __enter__(self):
		return self

	def __exit__(self, type, value, tback):
		TPDWorker.shutdown()

		sql.Q.join()

		if type != None:
			log.error("{name}: {msg}".format(name = value.__class__.__name__, msg = value))
			log.error("".join(traceback.format_exception(*sys.exc_info())))

	def start(self, cats = None, holidays = None):
		log.info("Starting TPD Calculations.")
		if cats == None:
			cats = CatFile.getCatList()

		if holidays == None:
			holidays = HolidayCatFile.getHolidayList()

		updates = []
		for cat in cats:
			updates.extend([(cat, False, False), (cat, True, False)])

		for holiday in holidays:
			updates.extend([(holiday, False, True), (holiday, True, True)])
		
		TPDWorker.started.set()
		TPDWorker.startup()

		random.shuffle(updates)

		sql.TPDData.clear()

		for item in updates:
			TPDWorker.Q.put(item)

	def stop(self):
		TPDWorker.shutdown()
		log.info("TPD Calculations finished.")

	@property
	def running(self):
		return TPDWorker.is_running()

	@classmethod
	def calc(cls, cats = None):
		if not cls.calculating.is_set():
			cls.calculating.set()
			with TPD() as tpd:
				tpd.start(cats)

				while tpd.running:
					time.sleep(settings.data.sleep)

				tpd.stop()
			cls.calculating.clear()
		else:
			while cls.calculating.is_set():
				time.sleep(settings.data.sleep)
		log.info("Tracks Per Day = {tpd}".format(tpd = cls.get()))
				

	@classmethod
	def get(cls):
		track_length = sql.TPDData.get()
		if track_length == 0:
			cls.calc()
			track_length = sql.TPDData.get()

		if "podcast_mean" not in cache:
			cache["podcast_mean"] = sql.PodcastFeedData.get_weighted_length()

		if "podcast_gap" not in cache:
			cache["podcast_gap"] = sql.PodcastFeedData.get_podcast_gap()


		podcast_weight = lib.podcastWeight(cache["podcast_mean"], track_length, cache["podcast_gap"])

		return lib.calcTPD(track_length) * (1 - podcast_weight)

	@classmethod
	def get_min(cls):
		return lib.minSize(cls.get())
